package net.cyndeline.misc

import scala.reflect.ClassTag

class Bag[E >: Null: ClassTag] extends ImmutableBag[E] {
  private var elements = new Array[E](64)
  private var bagSize = 0
  
  override def add(item: E) = {
    if (size == capacity) {
      grow()
    }
    
    elements(size) = item
    bagSize += 1
  }
  
  override def get(index: Int) = {
    if (index >= 0 && index < elements.length) {
      Option(elements(index))
    } else {
      None
    }
    
  }
  
  override def set(index: Int, item: E) {
    if (index >= capacity) {
      grow(index * 2)
    }
    
    /* Size shouldn't increase unless an actual component
     * is inserted where there was none.
     */
    if (item != null && elements(index) == null) {
      bagSize += 1
    } else if (item == null && elements(index) != null) {
      bagSize -= 1
    }
    
    elements(index) = item
  }
  
  override def remove(index: Int): Option[E] = {
    val removed = elements(index)
    elements(index) = null
    bagSize -= 1
    Option(removed)
  }
  
  override def removeLast(): Option[E] = {
    if (size > 0) {
      bagSize -= 1
      val removed = elements(bagSize)
      elements(bagSize) = null
      Option(removed)
      
    } else {
      None
    }
  }
  
  override def size: Int = bagSize
  
  override def capacity: Int = elements.length
  
  override def isEmpty: Boolean = size == 0
  
  override def clear() {
    var n = capacity - 1
    while (n >= 0) {
      elements(n) = null
      n -= 1
    }
  }
  
  override def contains(element: E): Boolean = {
    var n = capacity - 1
    while (n >= 0) {
      if (elements(n) == element) {
        return true
        
      } else {
        n -= 1
      }
    }
    
    false
  }
  
  private def grow() {
    val newCapacity = (elements.length * 3) / 2
    grow(newCapacity)
  }
  
  private def grow(newCapacity: Int) {
    val oldData = elements
    elements = new Array[E](newCapacity)
    Array.copy(oldData, 0, elements, 0, oldData.length)
  }
}